using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace DataPaper
{
    class BW
    {
        public readonly  Bitmap bmp;
        public readonly double first_u;
        public readonly double first_b;
        public readonly double first_w;
        public readonly double first_lb;

        public readonly Point top_left;
        public BW(Bitmap bmp,
            double init_u,
            Point init_top_left
            )
        {
            this.bmp = bmp;
            double initWhite;
            double initBlack;
            // Init B&W
            CalibrateBW(init_top_left, out initWhite, out initBlack, init_u);
            // Move more exactly
            CalibrateXY(ref init_top_left, initWhite, initBlack, init_u);
            // Re Init B&W
            CalibrateBW(init_top_left, out initWhite, out initBlack, init_u);
            {
                Point p = init_top_left;
                int low = int.MaxValue;
                for (int i = 0; i < 17; ++i)
                {
                    p = new Point(p.r, p.c + init_u);
                    CalibrateXY(ref p, initWhite, initBlack, init_u);
                    int l = LowBlackLevel(p.r, p.c, initWhite, initBlack, init_u);
                    if (l < low)
                        low = l;
                }
                this.first_u = (p.c - init_top_left.c) / 17.0;
                this.first_b = initBlack;
                this.first_w = initWhite;
                this.first_lb = low;
                this.top_left = init_top_left;
            }
        }


        public int Gray(int y, int x)
        {
            return bmp.GetPixel(x, y).B;
        }

        public void CalibrateBW(Point p1, out double w, out double b, double u)
        {
            b = 0;
            w = 0;
            int sum_b = 0;
            int sum_w = 0;
            for (int r = (int)(-u / 2); r <= u / 2; ++r)
                for (int c = (int)(-u / 2); c <= u / 2; ++c)
                    if (r * r + c * c < (u / 4.0) * (u / 4.0))
                    {
                        b += Gray(r + p1.r, c + p1.c);
                        sum_b++;
                    }
                    else
                    {
                        w += Gray(r + p1.r, c + p1.c);
                        sum_w++;
                    }
            b /= sum_b;
            w /= sum_w;
        }


        // Small value == good fit
        public double Fit(int rp, int cp, double w, double b, double u)
        {
            double fit = 0;
            double gray = (b + w) / 2;
            double quiteBlack = b + (w - b) / 4;
            double quiteWhite = b + (w - b) * 3 / 4;
            for (int r = (int)(-u / 2); r <= u / 2; ++r)
                for (int c = (int)(-u / 2); c <= u / 2; ++c)
                    if (r * r + c * c > (u / 4.0) * (u / 4.0))
                    {// White expected
                        int g = Gray(r + rp, c + cp);
                        if (g < quiteWhite)
                        {
                            ++fit;
                            if (g < quiteBlack)
                                ++fit;
                        }
                    }
                    else
                    {
                        int g = Gray(r + rp, c + cp);
                        if (g > quiteBlack)
                        {
                            ++fit;
                            if (g > quiteWhite)
                                ++fit;
                        }
                    }
            return fit;
        }

        private int BlackLevel(int rp, int cp, double w, double b, double u)
        {
            int nbBlack = 0;
            for (int relr = (int)(-u / 2); relr <= u / 2; ++relr)
                for (int relc = (int)(-u / 2); relc <= u / 2; ++relc)
                {
                    if (Gray(relr + rp, relc + cp) < (w + b) / 2)
                        ++nbBlack;
                }
            return nbBlack;
        }


        public int LowBlackLevel(int rp, int cp, double w, double b, double u)
        {
            int v = int.MaxValue;
            for (int relr = (int)(-u / 2); relr <= u / 2; ++relr)
                for (int relc = (int)(-u / 2); relc <= u / 2; ++relc)
                {
                    int bl = BlackLevel(rp + relr, cp + relc, w, b, u);
                    if (bl < v)
                        v = bl;
                }
            return v;
        }

        public bool IsBlack(int rp, int cp, double w, double b, int bl, double u)
        {
            int nbBlack = BlackLevel(rp, cp, w, b, u);
            return nbBlack >= bl;
        }

        public void CalibrateXY(ref Point p_black, double w, double b, double u)
        {
            int rs = p_black.r;
            int cs = p_black.c;
            double minFit = double.MaxValue;
            for (int rn = (int)(-u / 2); rn <= u / 4; ++rn)
                for (int cn = (int)(-u / 2); cn <= u / 4; ++cn)
                {
                    double f = Fit(rs + rn, cs + cn, w, b, u);
                    if (f < minFit)
                    {
                        minFit = f;
                        p_black = new Point(rs + rn, cs + cn);
                    }
                }
        }
    }
}
